//
//  ReactorKitTest3ViewController.swift
//  iOS-RxSwift-Tutorials_Example
//
//  Created by mengru.tian on 2020/9/24.
//  Copyright © 2020 CocoaPods. All rights reserved.
//

import UIKit
import RxSwift
import ReactorKit
import MBProgressHUD

//响应结果枚举
enum Result<T, E: Error> {
    case success(T)  //成功（里面是返回的数据）
    case failure(E)  //失败（里面是错误原因）
}
 
//失败情况枚举
enum GitHubServiceError: Error {
    case offline
    case githubLimitReached
}
 
//失败枚举对应的错误信息
extension GitHubServiceError {
    var displayMessage: String {
        switch self {
        case .offline:
            return "网络链接失败!"
        case .githubLimitReached:
            return "请求太频繁，请稍后再试!"
        }
    }
}

typealias SearchRepositoriesResponse = Result<(GitHubRepositories), GitHubServiceError>

extension GitHubNetworkService {
     
    //搜索资源数据
    func searchRepositories(query:String) -> Observable<SearchRepositoriesResponse> {
        return GitHubProvider.rx.request(.repositories(query))
            .filterSuccessfulStatusCodes()
            .mapObject(GitHubRepositories.self)
            .map{ .success($0) } //成功返回
            .asObservable()
            .catchError({ error in
                print("发生错误：",error.localizedDescription)
                //失败返回（GitHub接口对请求频率有限制，太频繁会被拒绝：403）
                return Observable.of(.failure(.githubLimitReached))
            })
    }
}

final class ViewReactor3: Reactor {
     
    //数据请求服务
    let networkService = GitHubNetworkService()
     
    //代表用户行为
    enum Action {
        case updateQuery(String?)  //查询条件内容改变
    }
     
    //代表状态变化
    enum Mutation {
        case setQuery(String?) //设置查询条件文字
        case setLoading(Bool) //设置搜索状态
        case setRepos(SearchRepositoriesResponse) //设置搜索结果
    }
     
    //代表页面状态
    struct State {
        var query: String?  //搜索文字
        var loading: Bool = false //当前正在加载
        var results: [GitHubRepository] = []  //搜索结果集
        var lastError: GitHubServiceError? //错误信息
        var totalCount: Int = 0 //搜索结果数
        var title: String {
            get {
                if query == nil || query!.isEmpty {
                    return "hangge.com"
                } else {
                    return "共有 \(totalCount) 个结果"
                }
            }
        }
    }
     
    //初始页面状态
    let initialState: State
     
    init() {
        self.initialState = State()
    }
     
    //实现 Action -> Mutation 的转换
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        //搜索文字内容变化
        case let .updateQuery(query):
            //如果查询条件为空则直接清空结果（不需要查询）
            guard !query!.isEmpty else { return Observable.just(Mutation.setQuery(query)) }
            //依次执行下面2个状态变化动作
            return Observable.concat([
                //设置搜索文字
                Observable.just(Mutation.setQuery(query)),
                //设置搜索状态
                Observable.just(Mutation.setLoading(true)),
                //设置搜索结果
                networkService.searchRepositories(query: query!)
                    //如果查询条件再次变化，但上一次搜索还未完成，上一次搜索会自动取消
                    .takeUntil(self.action.filter(isUpdateQueryAction))
                    .map(Mutation.setRepos),
                //设置搜索状态
                Observable.just(Mutation.setLoading(false))
                ])
        }
    }
     
    //实现 Mutation -> State 的转换
    func reduce(state: State, mutation: Mutation) -> State {
        //从旧状态那里复制一个新状态
        var state = state
         
        //根据状态变化设置响应的状态值
        switch mutation {
        //设置查询条件
        case let .setQuery(query):
            state.query = query
            //查询条件为空则直接清空结果
            if state.query!.isEmpty {
                state.results = []
                state.totalCount = 0
            }
        //设置搜索状态
        case let .setLoading(value):
            state.loading = value
        //设置响应结果（成功）
        case .setRepos(.success(let repositories)):
            state.results = repositories.items
            state.totalCount = repositories.totalCount
            state.lastError = nil
        //设置响应结果（失败）
        case .setRepos(.failure(let error)):
            state.results = []
            state.totalCount = 0
            state.lastError = error
        }
         
        //返回新状态
        return state
    }
     
    //判断当前的 action 是否是查询条件改变
    private func isUpdateQueryAction(_ action: Action) -> Bool {
        if case .updateQuery = action {
            return true
        } else {
            return false
        }
    }
}

class ReactorKitTest3ViewController: UIViewController,StoryboardView {
    typealias Reactor = ViewReactor3

    @IBOutlet weak var tableView: UITableView!
    
    //搜索栏
    var searchBar:UISearchBar!
     
    //活动指示器
    var loadingHUD:MBProgressHUD!
     
    var disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        title = "67.ReactorKit架构3：GitHub资源搜索样例"
        view.backgroundColor = .white
        
        test()
    }

    func test() {
        //创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        //创建表头的搜索栏
        self.searchBar = UISearchBar(frame: CGRect(x: 0, y: 0,
                                    width: self.view.bounds.size.width, height: 56))
        self.tableView.tableHeaderView =  self.searchBar
         
        //创建一个加载指示器
        self.loadingHUD = MBProgressHUD.showAdded(to: self.view, animated: false)
         
        //设置reactor，会自动触发bind()方法
        self.reactor = ViewReactor3()
         
        //单元格点击
        self.tableView.rx.modelSelected(GitHubRepository.self)
            .subscribe(onNext: {[weak self] item in
                //显示资源信息（完整名称和描述信息）
                self?.showAlert(title: item.fullName, message: item.description)
            }).disposed(by: disposeBag)
    }
    
    //处理绑定事件（该方法会在 self.reactor 变化时自动触发）
    func bind(reactor: ViewReactor3) {
         
        //Action（实现 View -> Reactor 的绑定）
        searchBar.rx.text.orEmpty.changed  //搜索框文字改变事件
            .throttle(0.3, scheduler: MainScheduler.instance)
            .distinctUntilChanged()
            .map(Reactor.Action.updateQuery)  //转换为 Action.updateQuery
            .bind(to: reactor.action)  //绑定到 reactor.action
            .disposed(by: disposeBag)
         
        // State（实现 Reactor -> View 的绑定）
        reactor.state.map { $0.results }  //得到最新搜索结果
            .bind(to: tableView.rx.items)(self.configureCell) //绑定到表格上
            .disposed(by: disposeBag)
         
        reactor.state.map { $0.title }  //得到最新标题文字
            .bind(to: navigationItem.rx.title) //绑定到导航栏上
            .disposed(by: disposeBag)
         
        reactor.state.map { !$0.loading }  //得到请求状态
            .bind(to: loadingHUD.rx.isHidden) //绑定到加载指示器上
            .disposed(by: disposeBag)
         
        //错误信息显示
        reactor.state.map{ $0.lastError }
            .subscribe(onNext: { [weak self] error in
                if let error = error, let view = self?.view {
                    print(error.displayMessage)
                    let hud = MBProgressHUD.showAdded(to: view, animated: true)
                    hud.mode = .text ////纯文本模式
                    hud.label.text = error.displayMessage
                    hud.removeFromSuperViewOnHide = true //隐藏时从父视图中移除
                    hud.hide(animated: true, afterDelay: 2)  //2秒钟后自动隐藏
                }
            })
            .disposed(by: disposeBag)
    }
     
    //单元格配置
    func configureCell(tableView: UITableView, row: Int, item: GitHubRepository)
        -> UITableViewCell {
            let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
            cell.textLabel?.text = item.name
            cell.detailTextLabel?.text = item.htmlUrl
            return cell
    }
     
    //显示消息
    func showAlert(title:String, message:String){
        let alertController = UIAlertController(title: title,
                                        message: message, preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)
        self.present(alertController, animated: true, completion: nil)
    }
}
